home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / ASMVEG2.ZIP / ASMVEG2.TXT next >
Text File  |  1996-12-23  |  14KB  |  340 lines

  1.       Assembly Language for Veggies (And C programmers)      Part 2.
  2.  
  3.  
  4. So, you've wound your way through part one, lashed out and bought the book and
  5. now you're about to leap into things in a big way, right?
  6.  
  7. OK... well here is where we make a bit of a start on things! We'll be looking
  8. first off at a simple routine that displays a number on the screen. sound
  9. simple? You Wait!
  10.  
  11. One of the more vital routines one uses from time to time [read all the time!]
  12. is a simple write number to screen routine. consider what isrequired here.. to
  13. take a number held wither in a register or memory and display it on the
  14. screen. simple?  you think about the work required to do it and you'll
  15. start to understand just how hard it is to do...
  16.  
  17. Firstly, consider the biggest number you wish to write... if it's 8 bits or
  18. less (0 -255) then you can use one routine, whilst a 16 bit (0-65535) routine
  19. would be more versatile, BUT if you need to do really big numbers (like free
  20. space on a hard disk for example) thena 32 bit routine would be called for...
  21.  
  22. Perhaps the most common of all is the 16 bit routine....
  23.  
  24. It is coded thus:
  25.  
  26. WRITE_ASCII:         PUSH AX
  27.          PUSH CX
  28.          PUSH DX
  29.          PUSH SI
  30.          MOV AX,DX
  31.          MOV SI,10
  32.          XOR CX,CX
  33.  
  34. NON_ZERO:         XOR DX,DX
  35.          DIV SI
  36.          PUSH DX
  37.          INC CX
  38.          OR AX,AX
  39.          JNE NON_ZERO
  40.  
  41. WRITE_DIGIT_LOOP:         POP DX
  42.          CALL WRITE_HEX_DIGIT
  43.          LOOP WRITE_DIGIT_LOOP
  44.  
  45. END_DECIMAL:         POP SI
  46.          POP DX
  47.          POP CX
  48.          POP AX
  49.          RET
  50.  
  51. WRITE_HEX_DIGIT:         PUSH DX
  52.          CMP DL,10
  53.          JAE HEX_LETTER
  54.          ADD DL,'0'
  55.          JMP SHORT WRITE_DIGIT
  56.  
  57. HEX_LETTER:         ADD DL,'A'-10
  58.  
  59. WRITE_DIGIT:         CALL PRT1
  60.          POP DX
  61.          RET
  62.  
  63.  
  64. PRT1:         PUSH AX
  65.          MOV AH,02
  66.          INT 021
  67.          POP AX
  68.          RET
  69.  
  70.  
  71. That is one BIG routine..... the Pascal version of which whould be :
  72.  
  73. program write_num
  74.  
  75. Var
  76.   number : word;
  77.  
  78. begin
  79.   number := 5285; {Say}
  80.   write(number);
  81. end.
  82.  
  83. We actually have three separate routines here, one called WRITE_ASCII which is
  84. the one we call, and two more subroutines which write_ascii calls itself. Can
  85. you name them?   They are referanced by CALL instructions...   They are
  86. write_hex_digit and prt1. write_hex_digit calls prt1 so we basicly have a 3
  87. level nested loop arrangement.
  88.  
  89. Looking complex yet?
  90.  
  91. Time to step into the code in more detail... When the routine is first called,
  92. the number to be displayed should be present in the DX register.
  93.  
  94. The first job our routine has to do is to convert the number in the register
  95. to decimal, and then into actuall ASCII digits suitable for writing to the
  96. screen. Here is where we get tricky...  We use a loop and some simple maths to
  97. derive the decimal equivilant, and the number of loops is equal to the number
  98. of digits in the final ascii number.
  99.  
  100. The first instruction you see is the PUSH command. What this does is put the
  101. contents of the named register upon the stack. In effect, this saves the
  102. contents in a more or less indestructable area for later recall.. One can
  103. safely fiddle with the contents of the register, safe in the knowledge that
  104. it's original contents can be recalled with the use of the POP instruction.
  105. note that PUSH's and POP's must be done in order...   if one does a push AX,
  106. push bx, then later does a pop ax, pop bx, the contents of ax and bx will be
  107. exchanged... it's the same as any other stack operation, so keep things in
  108. order!!
  109.  
  110. We save the AX, CX, DX snd SI registers....  WHY??    Think about this for a
  111. second or two..  We save these registers because we modify them in our
  112. routine! now, why is this important??  Because the main program may too be
  113. using them!
  114.  
  115. If you find this hard to understand, follow this analogy...  you have a Car
  116. radio in your car, tuned to your favourite station (MMM-FM 105!) .. you take
  117. it to the garage for a service. When you get it back you'd expect it to still
  118. be on MMM wouldn't you?  Of course!!   Now the average garage jock likes FOX
  119. better, so he sticks it on fox. If he returned the car to you still on fox,
  120. you'd be upset and annoyed.... if he was kind enough to return it to MMM
  121. before giving it back, you'd be none the wizer and go about your way happy as
  122. larry as the saying goes.
  123.  
  124. To save our main program from bieng upset (read crashed) by alteration of it's
  125. registers, our routine saves them on entry and (as you'll soon see) recalls
  126. them just before exiting.   Clear?
  127.  
  128. OK...  now the register-to-decimal routine...  involves the loop from
  129. NON_ZERO: to the JNE instruction BEFORE the WRITE_DIGIT_LOOP: label. before
  130. entry, some registers are setup...  AX is loaded with our initial value. (DX
  131. stil holds this as well) SI is loaded with 10 decimal and cx is made equal to
  132. zero (boolean logic dictates that an xor of any number with itself results in
  133. 0) by the XOR cx,cx instruction.
  134.  
  135. Firstly DX is zeroed.
  136. AX is ten divided by SI (10)  ... the result is a decimal digit in AX (The
  137. quotient) and a remainder in AX.
  138.  
  139. This bit of math goes like this:
  140.  
  141. Let's say you called the program with 36h in DX. 36h goes into AX, and 10d
  142. into CX. 36 hex is the same as 54 decimal. 36h(ex) divided by 10d(ecimal)
  143. equals 5 in AX and 4 in DX.
  144.  
  145. DX now holds our lest signivigant digit (the 1's unit of you like) - the
  146. number 4. DX is saved on the stack for use in a second.
  147.  
  148. CX is incremented from 0 to one. this counts the fact that 1 digit has been
  149. saved so far.
  150.  
  151. the or AX,AX basicly checks to see if ax is zero or not. the JNE stands for
  152. Jump not equal..  this decodes into if the previous math operation (or ax,ax)
  153. worked out to be equal(ie zero!) then go to the next instruction. if it was
  154. Not Equal, then go to the label NON_ZERO. it would go to NON_ZERO because AX
  155. has a 5 in it, and a 0 is needed to not jump! 
  156.  
  157. In the next loop we would then see the remander of 5 in AX still, and again
  158. the same thing happens...
  159.  
  160. 6 divided by 10 is a result of 0 (into AX) and a remainder (ie 0.6) into DX.
  161.  
  162. Again DX is saved on the stack, and CX in incremented. We now have 2 elements
  163. on the stack, and CX counts them. Ax is now equal to 0, so an or ax,ax proves
  164. true and the JNE results in a no jump, and the program reaches the
  165. WRITE_DIGIT_LOOP label for the first time.
  166.  
  167. At this point we have the hex number stored on the stack in MSB --> LSB format
  168. (just right for writing to screen!) and a count of the number of digits in CX.
  169.  
  170. now, here's a trick...
  171.  
  172. There's some special insttructions built into the 80xxx series that make use
  173. of the CX register... One of them is the LOOP command.
  174.  
  175. LOOP does this:  Decrements CX by 1. if CX=0, then it goes to the next
  176. instruction. If CX is not one, however, it jumps to the label (in this case
  177. WRITE_HEX_DIGIT) coded next to it. 
  178.  
  179. so there's a loop of 3 instructions to be done CX times. what happens is DX is
  180. popped off the stack, (that's the MSB, the number 5) and the subroutine
  181. WRITE_HEX_DIGIT is called. in the next loop, the number 4 appears in DX and
  182. the same routine is called...   note that the LOOP command actually decrements
  183. CX by one, saving us the job of doing it! Quite neat but a trap for young
  184. programmers...  At this point, CX will be equal to 0. 
  185.  
  186. 2 elements have been popped off the stack, so the stack is back at the point
  187. where we did the PUSH SI..
  188.  
  189. You may have guessed, write_hex_digit actually does the displaying, and we'll
  190. look at that in a tic.. but at this point the routine has done it's job and
  191. it's time to return to the calling program. We restore all the registers we
  192. saved with the POP commands.. (note how they go in exact reverse order to the
  193. PUSH's) then go back to our caller with a RET  (Short for RETURN [Just like
  194. BASIC!])
  195.  
  196. That's all there is to the main loop!
  197.  
  198. Now, on to the displaying a digit bit...
  199.  
  200. this should be fairly clear to you.. it's name indicates that it is also
  201. capable of displaying HEX letters as well...  we don't need to worry about
  202. that however, as all our numbers will be between 0 and 9...
  203.  
  204. the number comes into the routine in the DX register (I like using DX for
  205. number passing :-)   )
  206.  
  207. See if you can guess how it works... The numbers 0-9 appear in the ASCII table
  208. sequentially starting at number 30. The real value of '0' is 30.. got it??
  209.  
  210. If you want to work out the hex bit, JAE stands for jump if above or equal,
  211. whilst the ADD dl,'a'-10 must pick the letter, right?
  212.  
  213. It is important to realize that all this time we've been woring on the 16 bit
  214. DX register, but we've only been concerned with the bottom 4 bits!!  a bit of a
  215. waste, possibly, but it works and is just as functional as using the dl
  216. register. I've randomly mixed referance to dl and dx knowing that dl works on
  217. the BOTTOM (Remember l for lower, h for higher!) 8 bits of dx.
  218.  
  219. The contents of dl will now be 30+the original number, which is ascii for the
  220. digit 0-9. All that remains is to actually throw this digit onto the screen.
  221.  
  222. That's what PRT1 does..
  223.  
  224. By now, port1 should be self explanitory. Get a pen. Get paper. Scribble down
  225. how YOU think prt1 works. Take 2 minutes maximum. THEN and only THEN look at
  226. the next 2 lines...  you may use the book I recommended to look up the INT 021
  227. function (in fact, I insist you do!)
  228.  
  229. ANSWER: We use AH to indicate which INT 021 function we want, so we load it
  230. with 02, after saving it's orignial value on the stack so that when we return
  231. the calling program will have all registers the same. The calling program
  232. provides the ASCII digit in DL.. the routine expects itthere, so we simply
  233. call int 021, restor AX and return to the caller. Simple, eh! did you figure
  234. it out?
  235.  
  236. you've just learnt some valuable instructions and methods used in ASM. the 
  237. CALL -- RET sequence is the same as BASIC's gosub -- return sequence, the loop
  238. function repeats CX times, you can temporarily save registers on the stack.. 
  239.  
  240. Add anthing else you feel you're getting used to...  Quite a bit, isn't it!
  241. Don't say I didn't warn you!
  242.  
  243. If you wish to test this routine for yourself (and I suggest you play with it
  244. for a while) then code this is A86:
  245.  
  246. BEGIN:   mov DX,<stick in a hex value between 00000h and 0ffffh)
  247.          call WRITE_ASCII
  248.          int 020
  249.  
  250. ; at this point type in all hte code presented above as it appears. pay no
  251. ; regard to case - the assembler is not case sensitive and I do it for clarity
  252. ; only!! 
  253.  
  254. WRITE_ASCII: 
  255. .
  256. .
  257. .
  258. .
  259.  
  260. PRT1:
  261. .
  262. .
  263. .
  264. .
  265.          RET
  266.  
  267.  
  268. Assemble this with a random value. Calculate the decimal version (use a
  269. calculator or something!) and see if it works or not....
  270.  
  271. Hint:  FFFF = 65535     00FF = 256     08C4 = 2244    Honest!!
  272.  
  273. each time you run it, the number in DX will be printed to the screen..  If you
  274. feel confidant, fiddle round and see the effect of changing various bits..
  275. the worst you can do is lock up the machine!
  276.  
  277. as a challenge, save the CX register somewhere, and display it after the
  278. number to count the number rof digits in the number!
  279.  
  280. A hint: Use the prt1 routine tp rint a space character to separate the 2
  281. numbers, and don't use PUSH to save CX...  WHY NOT??
  282.  
  283.  
  284.             Some comments on structured programming
  285.  
  286.  
  287. You should all know what that is all about....
  288.  
  289. If not, it basicly says that you divide your chunks of code up into sections
  290. that only do one, fairly specific job, then call these chunks as you need. Each
  291. chunk (Well, subroutine or procedure are other names for them, but I'll use
  292. chunks! [just to be different]) should have one enty point (the bit you CALL)
  293. and one exit point (IE No JMP's into other chunks, no coding multiple RET's
  294. into the one chunk) and should not upset the operation of any other chunk (the
  295. reason for the saving registers on the stack)
  296.  
  297. Examine the above code..  you see the first chunk converts raw hex to a
  298. numerical digit. A second chunk is called to do the conversion to ASCII, and a
  299. third chunk to display it on screen. Each chunk is independant (save the
  300. actual parameters passed to it and it's output) and can operate from any
  301. number of calling routines... the WRITE_HEX_DIGIT routine could be called
  302. directly with a hex digit in DL and would print a HEX digit to the screen.
  303.  
  304. This is the very essence of modular (Structured) programming. If one does not
  305. follow this system (in any language, but most importantly in ASM where you
  306. code tends to become impossible to understand very easily) then things soon
  307. become a real shit mess that even you cannot follow, much less debug!
  308.  
  309. Shit mess code that has evolved without thought to structure is often referred
  310. to as SPAGHETTI CODING - because it's like trying to sort out a bowl of said
  311. material - near impossible to find the true start and end for all the straggly
  312. bits!
  313.  
  314. Librarys of usefull routines are soon built up from this (I use the above
  315. routine ALL the time in my text based programs) that you know you can drop in
  316. any program when required and not have to change anything to accomidate it..
  317. I cannot stress enough how much simpler a set of worked out, debugged, easy to
  318. call routines makes your programming life!
  319.  
  320.  
  321.    ------------------------------------------------------------------
  322.  
  323.  
  324. That just about draws together the end of lesson 2 unfortunately...  It's
  325. grown  fair bit larger than I'd first hoped, but who cares, I think if I made
  326. it any smaller, you'd loose track of it...
  327.  
  328. Next lesson : using A86 and D86 together, more programming examples and a look
  329. at the INT 021 functions...
  330.  
  331. In the meantime, read you book from cover to cover. digest as much as you can,
  332. and what you can't, don't worry about...  you should be beginnig to understand
  333. the register calling convention used with INT 021 - so I suggest you read up
  334. on that a bit! I'll have a lot to say about MS-DOS and it's problems, quirks,
  335. hassles etc....  as well as some undoccumented stuff as time goes on...
  336.  
  337. Until then, good coding!                             .\\erlin      8/91
  338.  
  339.  
  340.